home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
130 MIDI Tool Box
/
130 MIDI Tool Box.iso
/
driver
/
midi.asm
next >
Wrap
Assembly Source File
|
1987-05-11
|
38KB
|
1,065 lines
page 58,132
; file: MIDI.ASM
TITLE Z8470 MIDI driver for MSDOS
SUBTTL DESCRIPTION
;THINGS TO ADD: (DELETE THESE LINES AS YOU IMPLIMENT THEM)
;data errors reported at the time you get to that byte in read buffer
;
;
; Loadable midi device driver for msdos.
; Written by: Mike Higgins
; Copyright (c) April 1984 by The Computer Entomologist.
;
;************************************************************************
; This file implements an interrupt driven character device driver
;for the midi board described in the article: "A MIDI Project", by Jay
;Kupicky, BYTE Magazine, June 1986, V.11 N.6. The driver will work on any
;similar board that uses the Z8470 Z80 DART chip. This code should work
;with several DART chips on one board, if you change the appropriate
;counters, add structures and buffers for each UART, and check the interrupt
;and initialization code.
; Why write or use such a driver? Because the code written by
;Jay Kubicky could only be compiled on one specific C compiler, and
;required non-standard communication channels between the main code and
;the assembly driver.
; This driver implements the MIDI board as a standard MSDOS character
;device that can be called from any program written in any language. Even
;interpretative BASIC can open this device and perform any function that
;the board is capable of. All the communication with the driver is
;done with standard reads,writes, and IOCTL calls. The character input/
;output is the MIDI data, and the IOCTL data allows a program to find the
;state of the MIDI UARTS, and to perform initialization functions. (Test
;for data available or error status, flush buffers, change echo/thru
;modes). Additionally, there is a read-with-timeout feature that
;allows the driver to return to you if no data arrives after a specified
;number of milliseconds.
; This driver implements a 128 byte buffer on input and output to
;make catching all the MIDI data as easy as possible for you programs.
;The output buffer allows your programs to send multiple byte commands
;without having to wait on each byte: your processing can continue while
;the driver sends the bytes out one at a time for you.
; There is also a "MIDI out/through" option in this driver that
;will copy, if enabled, any incoming bytes immediately to the associated
;MIDI out port. I call this a "poor man's MIDI merge" because it will
;scramble MIDI commands together if they arrive at the output buffer
;too close together. But it does have some limited applications. Note
;that a very intelligent MIDI merge program would be easy to write as an
;application on top of this board and driver. (Requires both UARTs hooked
;up with MIDI drivers and receivers, like mine is).
; While initializing the uarts, this driver also initializes the on
;board clock chip. You can get direct access to this clock for timing midi
;events, and the driver uses this clock to count time for the read-with-
;timeout feature. The delta times are in "digital" milliseconds, so the
;value changes 1024 times each second. The two byte clock value from timer2
;will allow you to time anything that is 64 seconds or less, which should be
;long enough for any typical MIDI use. (You will have to store delta times,
;not absolute time). On my version of the board, timer2 overflows into
;timer3, so this can be considered a 16bit extension of the regular timer.
;This results in a four byte clock that only overflows after 136
;years. If you can keep your PC running continuously that long (without
;re-booting) I'll be very impressed!
;
;************************************************************************
;
; Permission is hereby granted to use or distribute this software
;without any restrictions. You may make copies for yourself or your
;friends. You may include it in any hardware or software product that you
;sell for profit.
;
; This software is distributed as is, and is not guaranteed to work
;on any particular hardware/software configuration. Furthermore, no
;liability is granted with this software: the user takes responsibility for
;any damage this software may do to his system.
;
; Nasty notices aside, if you have any questions about this software,
;you can reach me at the address below. If you implement any new features or
;find (and fix!) any bugs, I would be happy to hear from you.
;
; Mike Higgins
; The Computer Entomologist
; P.O. Box 197
; Duncans Mills, CA 95430
;
;
; ASSEMBLY INSTRUCTIONS:
; MASM MIDI,MIDI,MIDI,NUL
; LINK MIDI,MIDI,MIDI,NUL
; EXE2BIN MIDI
; COPY MIDI.BIN A: (IF NOT THERE ALREADY)
; ADD THE FOLLOWING LINE TO A:CONFIG.SYS:
; DRIVER=MIDI.BIN
; RE-BOOT YOUR SYSTEM AND IT'S THERE!
;
; NOTE:
;
; There are hooks in the driver for features that do not work
; yet. So don't expect all of these things to work just
; because there are comments about them or bits to set for them.
; Write me if you do implement any of this stuff.
SUBTTL DEFINITIONS
PAGE
;
; DEVICE TYPE CODES
DEVCHR EQU 08000h ;THIS IS A CHARACTER DEVICE
DEVBLK EQU 0H ;THIS IS A BLOCK (DISK) DEVICE
DEVIOC EQU 04000H ;THIS DEVICE ACCEPTS IOCTRL REQUESTS
DEVNON EQU 02000H ;NON IBM DISK DRIVER
DEVSPC EQU 010H ;CONSOLE ACCEPTS SPECIAL INTERUPT 29
DEVCLK EQU 08H ;THIS IS THE CLOCK DEVICE
DEVNUL EQU 04H ;THIS IS THE NUL DEVICE
DEVSTO EQU 02H ;THIS IS THE CURRENT STANDARD OUTPUT DEVICE
DEVSTI EQU 01H ;THIS IS THE STANDARD INPUT DEVICE
;
; ERROR STATUS BITS
STSERR EQU 08000H ;GENERAL ERROR, SEE LOWER ORDER BITS FOR REASON
STSBSY EQU 0200H ;DEVICE IS BUISY
STSDNE EQU 0100H ;REQUEST IS COMPLETED
; ERROR REASON VALUES FOR LOWER ORDER BITS.
ERRWP EQU 0 ;WRITE PROTECT ERROR
ERRUU EQU 1 ;UNKNOWN UNIT
ERRDNR EQU 2 ;DRIVE NOT READY
ERRUC EQU 3 ;UNKNOWN COMMAND
ERRCRC EQU 4 ;CYCLIC REDUNDANCY CHECK ERROR
ERRBSL EQU 5 ;BAD DRIVE REQUEST STRUCTURE LENGTH
ERRSL EQU 6 ;SEEK ERROR
ERRUM EQU 7 ;UNKNOWN MEDIA
ERRSNF EQU 8 ;SECTOR NOT FOUND
ERRPOP EQU 9 ;PRINTER OUT OF PAPER
ERRWF EQU 10 ;WRITE FAULT
ERRRF EQU 11 ;READ FAULT
ERRGF EQU 12 ;GENERAL FAILURE
;
;
; DEFINE THE PORT OFFSETS AND IMPORTANT BOARD CONSTANTS
MIDDAT EQU 0 ;DATA REGESTER
MIDCON EQU 2 ;CONTROL REGESTER
MIDB EQU 1 ;BITS TO FORCE IO ADDRESS TO UART B WITH OR
MIDA EQU NOT MIDB ;MASK TO FORCE IO ADDR TO UART A WITH AND
;
; BIT ASSIGNMENTS FOR THE STATUS WORD IN EACH UNIT STRUCTURE
OUTINT EQU 1 ;CHARACTER SENT OUT, INTERUPT SHOULD BE COMMING
OUTHRU EQU 2 ;INPUT IS ECHOED THRU OUTPUT (POOR MANS MIDI OUT/THRU
;
; BIT ASSIGNMENTS FOR THE ERVAL WORD IN EACH UNIT STRUCTURE
ERITMO EQU 1 ;INPUT TIMEOUT ERROR
EROTMO EQU 2 ;OUTPUT TIMEOUT ERROR
ERIBFO EQU 4 ;INPUT BUFFER OVERFLOW
EROBFO EQU 8 ;OUPUT BUFFER OVERFLOW
ERPARE EQU 10H ;PARITY ERROR (NOT ENABLED)
ERFRAM EQU 20H ;FRAMING ERROR
ERRXOV EQU 40H ;RECEVER OVERRUN (NOT LIKELY)
;
;BIT DEFINITIONS FOR ALL THE BITS/REGESTERS IN THE Z8470 ZILOG Z80 DART CHIP
;
;WRITE REGESTER 0 BITS
ZRESET EQU 010H ;RESET EXT/STATUS INTERUPTS
ZCHANR EQU 018H ;CHANNEL RESET
ZRXINT EQU 020H ;ENABLE INT ON NEXT Rx CHARACTER
ZTXINT EQU 028H ;RESET Tx INT PENDING
ZERRES EQU 030H ;ERROR RESET
ZREINT EQU 038H ;RETURN FROM INT (CHANNEL A ONLY)
;WRITE REGESTER 1 BITS
W1EXTI EQU 001H ;EXT INT ENABLE
W1TXINT EQU 002H ;Tx INT ENABLE
W1STAT EQU 004H ;STATUS EFFECTS VECTOR (CHANNEL B ONLY)
W1RXDIS EQU 000H ;Rx INT DISABLE
W1RX1ST EQU 008H ;Rx INT ON FIRST CHARACTER
W1RXALP EQU 010H ;Rx INT ON ALL CHARACTERS (PARITY AFFECTS VECTOR)
W1RXALL EQU 018H ;Rx INT ON ALL CHARACTERS (PARITY DOESN'T AFF. VEC)
W1WAITR EQU 020H ;WAIT/READY ON R/T
W1WAITF EQU 040H ;WAIT/READY FUNCTION
W1WAITE EQU 080H ;WAIT/READY ENABLE
;WRITE REGESTER 3 BITS
W3RXEN EQU 001H ;Rx ENABLE
W3AUTO EQU 020H ;AUTO ENABLES
W3RX5 EQU 000H ;Rx 5 BITS/CHAR
W3RX7 EQU 040H ;Rx 7 BITS/CHAR
W3RX6 EQU 080H ;Rx 6 BITS/CHAR
W3RX8 EQU 0C0H ;Rx 8 BITS/CHAR
;WRITE REGESTER 4 BITS
W4PARE EQU 001H ;PARITY ENABLE
W4PEVN EQU 002H ;PARITY IS EVEN
W4PODD EQU 000H ;PARITY IS ODD
W4STOP1 EQU 004H ;ONE STOP BITS
W4STP15 EQU 008H ;1.5 STOP BITS
W4STOP2 EQU 00CH ;TWO STOP BITS
W4X1 EQU 000H ;X1 CLOCK MODE
W4X16 EQU 040H ;X16 CLOCK MODE
W4X32 EQU 080H ;X32 CLOCK MODE
W4X64 EQU 0C0H ;X64 CLOCK MODE
;WRITE REGESTER 5 BITS
W5RTS EQU 002H ;SET RTS BIT IN OUTPUT
W5TXEN EQU 008H ;Tx ENABLE
W5BREAK EQU 010H ;SEND BREAK
W5TX5 EQU 000H ;Tx 5 BITS/CHARACTER
W5TX7 EQU 020H ;Tx 7 BITS/CHAR
W5TX6 EQU 040H ;Tx 6 BITS/CHAR
W5TX8 EQU 060H ;Tx 8 BITS/CHAR
W5DTR EQU 080H ;SET DTR BIT IN OUTPUT
;READ REGESTER 0 BITS
RXCHAR EQU 001H ;CHARACTER HAS BEEN RECEIVED
INTPEND EQU 002H ;INTERUPT IS PENDING (CHANNEL A ONLY)
TXFREE EQU 004H ;Tx BUFFER IS EMPTY
CD EQU 008H ;CARRIER DETECT LINE IS ON
RI EQU 010H ;RING INDICATOR LINE IS ON
CTS EQU 020H ;CLEAR TO SEND LINE IS ON
BREAK EQU 080H ;BREAK HAS BEEN DETECTED
;READ REGESTER 1 BITS
R1ALL EQU 001H ;ALL SENT (Tx BUFFER?)
R1PERR EQU 010H ;PARRITY ERROR
R1RXOVR EQU 020H ;Rx OFERRUN ERROR
R1FRAME EQU 040H ;FRAMING ERROR
;READ REGESTER 2 BITS
VECMASK EQU 006H ;REASON-FOR-INTERUPT BITS
VECB EQU 008H ;UART B FIRED INTERUPT
;
;REGESTER OFFSETS AND BIT ASSIGNMENTS FOR THE CLOCK TIMER CHIP
TIMER1 EQU -3 ;OFFSET FROM CONTROL REGESTER TO TIMER DATA 1
TIMER2 EQU -2 ;TIMER 2 DATA REGESTER
TIMER3 EQU -1 ;TIMER 3
T1 EQU 0 ;CONTROLLER TIMER 1 SELECT VALUE
T2 EQU 040H ;SELECT TIMER 2
T3 EQU 080H ;SELECT TIMER 3
LMSB EQU 030H ;READ TIMER REGESTERS IN LITTLE ENDIAN MODE
SHOOT EQU 002H ;ONE SHOT MODE
RATE EQU 004H ;RATE GENERATOR MODE
SQUARE EQU 006H ;SQUARE WAVE MODE
LATCH EQU 0 ;LATCH THE CURRENT VALUES FOR READING
;
SUBTTL DRIVER LIST HEAD
PAGE
;*************************************************************************
;
; BEGENING OF DRIVER CODE.
;
DRIVER SEGMENT
ASSUME CS:DRIVER,DS:DRIVER,ES:DRIVER
; ORG 0 ;DRIVERS START AT 0
MIDIB:
DW MIDIA,-1 ;POINTER TO NEXT DEVICE: DOS FILLS IN SEG.
DW DEVCHR OR DEVIOC ;CHARACTER DEVICE, IOCTL ALLOWED
DW STRATEGY ;OFFSET TO STRATEGY ROUTINE.
DW REQUESTB ;OFFSET TO "INTERUPT" ENTRYPOINT.
DB "MIDIB " ;DEVICE NAME.
MIDIA:
DW -1,-1 ;POINTER TO NEXT DEVICE: END OF LINKED LIST.
DW DEVCHR OR DEVIOC ;THIS DEVICE IS CHARACTER IOCTL
DW STRATEGY ;STRATEGY ROUTINE
DW REQUESTA ;I/O REQUEST ROUTINT
DB "MIDIA "
debug dd 0b0000000h
notify macro char
; local store
; push es
; push di
; push ax
; mov al,'&char'
; les di,CS:debug
; stos byte ptr [di]
; inc di
; cmp di,80*25*2
; jl store
; xor di,di
;store:
; mov word ptr CS:debug,di
; mov al,'<'
; stos byte ptr es:[di]
; pop ax
; pop di
; pop es
endm
SUBTTL DRIVER INTERNAL DATA STRUCTURES
PAGE
;
MIDI_UNITS EQU 2 ;NUMBER OF UNITS THIS DRIVER IS BUILT FOR
MIDI_VEC DW 028H ;INTERUPT VECTOR ADDRESS (NOT VECTOR NUMBER)
CLOCK_PORT DW 0FFA7H ;PORT OF CLOCK CONTROL REGESTER
;
UNIT STRUC ;EACH UNIT HAS A STRUCTURE DEFINING IT'S STATE:
PORT DW ? ;I/O PORT ADDRESS
STATUS DW ? ;STATUS BITS
TIMEOUT DW ? ;TIMEOUT CONSTANT ON READ OR WRITE REQUEST
ERVAL DW ? ;BIT ENCODED ERRORS SINCE LAST YOU LOOKED
EROFF DW ? ;OFFSET TO FIRST CHARACTER AFTER ERROR
IFIRST DW ? ;OFFSET TO FIRST CHARACTER IN INPUT BUFFER.
IAVAIL DW ? ;OFFSET TO NEXT AVAILABLE BYTE.
IBUF DW ? ;POINTER TO 128 BYTE INPUT BUFFER.
OFIRST DW ? ;OFFSET INTO FIRST CHARACTER IN OUTPUT BUFFER
OAVAIL DW ? ;OFFSET INTO NEXT AVAIL BYTE IN OUTPUT BUFFER
OBUF DW ? ;POINTER TO 128 BYTE OUTPUT BUFFER
UNIT ENDS
; TABLE OF STRUCTURES FOR EACH MIDI UNIT
; THE STRUCTURES FOR ALL THE UARTS MUST BE ORGANIZED TOGETHER INTO
; A CONTIGUOUS TABLE, BECAUSE THE INTERUPT SERVICE ROUTINE SCANS
; THROUGH THEM WHILE DETERMINING WHICH DART FIRED THE INTERUPT.
; SIMILARLY, THE STRUCTURES FOR THE TWO UARTS IN EACH DART MUST BI
; IN A-B ORDER IN THIS TABLE.
;
MIDI_TABA:
UNIT <0FFA0H,0,-1,0,-1,0,0,INABUF,0,0,OUTABUF>
UNIT_SIZE EQU $-MIDI_TABA
MIDI_TABB:
UNIT <0FFA1H,0,-1,0,-1,0,0,INBBUF,0,0,OUTBBUF>
;IF THE BUFFER SIZE IS A POWER OF TWO, THE PROCESS OF KEEPING
;THE OFSETTS WITHIN THE BOUNDS OF THE BUFFER IS GREATLY
;SIMPLIFIED. IF YOU MODIFY THE BUFFER SIZE, KEEP IT A
;POWER OF 2, AND MODIFY THE MASK ACCORDINGLY.
BUFSIZ EQU 128 ;INPUT BUFFER SIZE
BUFMSK EQU 127 ;MASK FOR CALCULATING OFFSETS MODULO BUFSIZ
INABUF DB BUFSIZ DUP (?)
INBBUF DB BUFSIZ DUP (?)
OUTABUF DB BUFSIZ DUP (?)
OUTBBUF DB BUFSIZ DUP (?)
;
; STRUCTURE OF AN I/O REQUEST PACKET STATIC HEADER
;
PACK STRUC
LEN DB ? ;LENGTH OF RECORD
PRTNO DB ? ;UNIT CODE
CODE DB ? ;COMMAND CODE
STAT DW ? ;RETURN STATUS
DOSQ DD ? ;UNUSED DOS QUE LINK POINTER
DEVQ DD ? ;UNUSED DRIVER QUE LINK POINTER
MEDIA DB ? ;MEDIA CODE ON READ/WRITE
XFER DW ? ;XFER ADDRESS OFFSET
XSEG DW ? ;XFER ADDRESS SEGMENT
COUNT DW ? ;TRANSFER BYTE COUNT.
PACK ENDS
;
; THE FOLLOWING TWO WORDS IS THE STORAGE AREA FOR THE REQUEST PACKET
; ADDRESS, SENT TO ME BY A STRATEGY ROUTINE CALL.
; AS REQUESTED BY THE MSDOS DRIVER MANUAL, I AM "THINKING
; ABOUT" THE FUTURE, SO I`M DESIGNATING THIS POINTER AS THE QUEUE
; LIST HEAD FOR REQUESTS TO THIS DRIVER.
;
PACKHEAD DD 0
;
; THE STRATEGY ROUTINE ITSELF.
PUBLIC STRATEGY
STRATEGY PROC FAR
;SQUIRREL AWAY THE POINTER FOR LATER.
MOV WORD PTR CS:PACKHEAD,BX ;STORE THE OFFSET,
MOV WORD PTR CS:PACKHEAD+2,ES ;AND THE SEGMENT.
RET
STRATEGY ENDP
SUBTTL REQUEST ROUTINES
PAGE
; I/O REQUEST ROUTINES
PUBLIC REQUESTA
REQUESTA: ;MIDIA HAS BEEN REQUESTED
PUSH SI ;SAVE SI SO YOU CAN
MOV SI,OFFSET MIDI_TABA ;GET THE DEVICE UNIT TABLE ADDRESS.
JMP GEN_REQUEST ;THE GENERIC DRIVER DOES THE REST.
REQUESTB: ;MIDIB HAS BEEN REQUESTED TO DO SOMETHING
PUSH SI ;SAVE SI
MOV SI,OFFSET MIDI_TABB ;GET UNIT TABLE TWO`S ADDRESS
GEN_REQUEST:
PUSHF ;I REQUIRE DIRECTION FLAG CLEARED, SO I SAVE
CLD ;THE FLAGS AND CLEAR THEM HERE.
PUSH AX ;SAVE ALL THE REGESTERS, YOU MAY NOT
PUSH BX ;NEED THEM ALL, BUT YOU WILL REGRET IT
PUSH CX ;IF YOU FORGET TO SAVE JUST ONE OF THEM.
PUSH DX
PUSH DI
PUSH BP
PUSH DS
PUSH ES
PUSH CS ;COPY THE CS REGESTER
POP DS ;INTO THE DS TO ACCESS MY DATA
LES BX,PACKHEAD ;RECOVER THE POINTER TO THE PACKET.
MOV AL,ES:CODE[BX] ;GET THE FUNCTION REQUEST CODE,
MOV AH,0 ;MAKE IT INTO A WORD,
SAL AX,1 ;CONVERT TO A WORD OFFSET,
MOV DI,AX ;AND ADD TO THE TABLE START ADDRESS
MOV AX,STSERR OR ERRUC ;SEND UNKNOWN COMMAND ERROR FOR EXIT
JMP MIDI_FUNCS[DI] ;JUMP TO THE APPROPRIATE ROUTINE
;
; TABLE OF OFFSETS TO ALL THE DRIVER FUNCTIONS
;
MIDI_FUNCS DW MIDI_INIT ;INITIALIZE DRIVER
DW EXIT ;MEDIA CHECK (BLOCK DEVICES ONLY)
DW EXIT ;BUILD BPB (BLOCK DEVICES ONLY)
DW IOCTLIN ;IOCTL INPUT
DW READ ;READ
DW EXIT ;NON-DESTRUCTIVE READ
DW EXIT ;INPUT STATUS
DW EXIT ;FLUSH INPUT BUFFER
DW WRITE ;WRITE
DW WRITE ;WRITE WITH VERIFY
DW EXIT ;OUTPUT STATUS
DW EXIT ;FLUSH OUTPUT BUFFER
DW IOCTLOUT ;IOCTL OUTPUT
;
; EXIT FROM DRIVER REQUEST
; CALL WITH AX= RETURN STATUS VALUE
EXITP PROC FAR
EXIT:
LES BX,PACKHEAD ;RETREIVE POINTER TO PACKET
; OR AX,STSDNE ;SET THE DONE BIT IN IT.
MOV ES:STAT[BX],AX ;STORE THE STATUS BACK IN THE PACKET.
POP ES ;RESTORE ALL THE REGESTERS
POP DS
POP BP
POP DI
POP DX
POP CX
POP BX
POP AX
POPF
POP SI
RET
EXITP ENDP
SUBTTL READ DATA REQUEST ROUTINE
PAGE
;
; ALL THE FOLLOWING ROUTINES ARE CALLED WITH THE SAME CONTEXT
; FROM THE REQUEST ROUTINE:
; - ES:BX POINTS TO THE I/O PACKET.
; ROUTINES CAN MUCK UP THESE TWO REGESTERS IF THEY WANT, AS EXIT
; WILL RESTORE THEM BEFORE IT TRIES TO SEND THE STATUS WORD BACK.
; - CS: AND DS: POINT TO THE BEGENING OF THE DRIVER SEGMENT.
; - DS:SI POINTS TO THE DEVICE UNIT TABLE DESCRIBING THE PARTICULAR
; PORT BEING ACCESSED.
; - ALL OTHER REGESTERS ARE AVAILABLE FOR USE, THE EXIT ROUTINE
; RESTORES THEM ALL BEFORE RETURNING TO MSDOS.
; ALL THE FOLLOWING ROUTINES SHOULD EXIT BY DOING A JMP
; TO THE EXIT ROUTINE. EXIT ASSUMES THAT AX
; CONTAINS EITHER ZERO, OR THE ERROR BIT SET AND A VALID ERROR
; RETURN VALUE IN THE LOW ORDER BITS. EXIT SETS THE DONE BIT IN
; THIS VALUE FOR YOU BEFORE IT RETURNS TO MSDOS.
;
SUBTTL READ REQUEST ROUTINE
; READ DATA FROM DEVICE
;
READ:
CALL GET_CLOCK ;GET CLOCK CURRENT VALUE FOR READ-
MOV DX,AX ;WITH-TIMEOUT TEST, AND STORE IT IN DX
MOV CX,ES:COUNT[BX] ;GET THE REQUESTED NUMBER OF BYTES
LES DI,DWORD PTR ES:XFER[BX] ;DI IS OFFSET TO USER BUFFER
MOV BX,TIMEOUT[SI] ;GET THE TIMEOUT LIMIT.
RLUP:
MOV AX,EROFF[SI] ;CHECK TO SEE IF IT'S TIME
CMP AX,IFIRST[SI] ;TO REPORT AN ERROR
JE ERR_READ
CALL GET_IN ;GET NEXT CHAR FROM INPUT BUFFER
CMP AH,0 ;WAS THERE ONE?
JNE READ_WAIT ;NO, TEST FOR TIMEOUT
STOS BYTE PTR[DI] ;YES,WRITE THIS BYTE OUT
;ALTHOUGH MSDOS NEVER, TO MY KNOWLEDGE, ASKS FOR MORE THAN
;ONE STUPID CHARACTER AT A TIME, I LOOP ON THE REQUEST SIZE
;SO THAT THIS DRIVER WILL STILL WORK ON THAT GLORIOUS DAY
;WHEN SOMEBODY ASKS FOR MORE THAN ONE.
LOOP RLUP ;KEEP GOING IF YOU WERE REQUESTED.
MOV AX,STSDNE ;RETURN NO ERRORS IN AX IF DONE.
JMP EXIT
ERR_READ: ;NOW IS THE TIME TO REPORT AN INPUT ERROR
MOV AX,-1 ;DISABLE THE ERROR CHECKING BY
MOV EROFF[SI],AX ;SETTING OFFSET TO FFFF
OR ERVAL[SI],ERIBFO ;SET INPUT BUFFER OVERFLOW BIT
JMP ERR_RET
READ_WAIT:
CMP BX,0FFFFH ;IF THE TIMEOUT VALUE IS MAX INT,
JE RLUP ;LOOP BACK UP FOREVER
CALL GET_CLOCK
SUB AX,DX ;CALCULATE THE DELTA TIME
NEG AX
CMP AX,BX ;COMPARE AGAINST THE TIMOUT LIMIT
JB RLUP ;LOOP BACK IF TIME IS STILL OK
OR ERVAL[SI],ERITMO ;TELL THEM A TIMEOUT ERROR HAPPENED
ERR_RET:
LES BX,PACKHEAD ;GET IO PACKET BACK
SUB ES:COUNT[BX],CX ;TELL HIM HOW MANY BYTES MADE IT, AND
MOV AX,STSBSY ;RETURN A READ
JMP EXIT ;FAULT ERROR.
;
SUBTTL WRITE REQUEST ROUTINE
PAGE
; OUTPUT DATA TO DEVICE
;
WRITE:
CALL GET_CLOCK ;GET CLOCK VALUE FOR TIMEOUT TEST
MOV DX,AX ;DX WILL CONTAIN STARTING TIME
MOV CX,ES:COUNT[BX] ;GET BYTE COUNT,
LES DI,DWORD PTR ES:XFER[BX] ;GET XFER ADDRESS
MOV BX,TIMEOUT[SI] ;GET TIMEOUT LIMIT
WLUP:
MOV AL,ES:[DI] ;GET THE NEXT CHAR DOS SENT YOU
CALL PUT_OUT ;ATTEMPT TO PUT IN IN OUTPUT BUFFER
CMP AH,0 ;DID IT WORK?
JNE WWAIT ;NO, GO TEST FOR TIMEOUT
INC DI ;SKIP TO NEXT BYTE
CALL START_OUTPUT ;START THE XMITTER IF NECC.
LOOP WLUP ;YES, GO GET NEXT CHAR.
MOV AX,STSDNE ;RETURN SUCCESS
JMP EXIT
WWAIT:
CALL GET_CLOCK ;GET CURRENT CLOCK TIME
SUB AX,DX ;CALCULATE NUMBER OF MILISECONDS SINCE START
NEG AX
CMP AX,BX ;HAVE WE WAITED LONG ENUF?
JB WLUP ;NO, KEEP TRYING
OR ERVAL[SI],EROTMO ;TELL THEM OUTPUT TIMED OUT
LES BX,PACKHEAD ;GET IO PACKET BACK
SUB ES:COUNT[BX],CX ;TELL HIM HOW MANY BYTES MADE IT, AND
MOV AX,STSBSY ;YES, RETURN A WRITE FAULT ERROR
JMP EXIT
;
SUBTTL I/O CONTROL READ REQUEST
PAGE
;
; IOCONTROL READ REQUEST, RETURN INTERNAL STRUCTURE
;
IOCTLIN:
MOV CX,ES:COUNT[BX] ;GET THE REQUESTED NUMBER OF BYTES
MOV DI,ES:XFER[BX] ;DI IS OFFSET TO USER BUFFER
MOV DX,ES:XSEG[BX] ;SEGMENT IS LAST I NEED FROM PACKET,
MOV ES,DX ;NOW ES:DI POINTS TO USER BUFFER.
CMP CX,UNIT_SIZE ;ONLY WORKS WHEN YOU GIVE ME A
JE DOIOCIN ;RIGHT SIZED BUFFER TO STOMP ON.
MOV AX,STSERR OR ERRBSL ;RETURN AN ERROR IF NOT RIGHT.
JMP EXIT
DOIOCIN:
REP MOVSB ;JUST COPY THE STRUCTURE TO CALLER
SUB SI,UNIT_SIZE ;POINT BACK AT UNIT STRUCTURE
MOV AX,0 ;SO YOU CAN ZERO THE
MOV ERVAL[SI],AX ;ERROR WORD EVERY TIME THEY READ IT
MOV AX,STSDNE ;RETURN NO ERRORS
JMP EXIT
;
SUBTTL I/O CONTROL WRITE REQUEST ROUTINE
PAGE
;
; I USE THIS COMMAND TO COMMUNICATE VARIOUS SPECIAL COMMANDS
; TO THE DRIVER. EACH BYTE SENT IS A SEPARATE COMMAND TO FLUSH A
; BUFFER, CLEAR STATUS, SET SPECIAL FLAGS, ETC.
;
IOCTLOUT:
MOV CX,ES:COUNT[BX] ;GET THE BYTE COUNT
LES DI,DWORD PTR ES:XFER[BX] ;AND THE ADDRESS
IOWRITE: ;LOOP FOR ALL IO CONTROL WRITE BYTES
MOV BL,ES:[DI] ;GET NEXT BYTE
INC DI ;SKIP TO FOLLOWING ONE
AND BX,7 ;CHOP OUT LOWER 3 BITS
SHL BX,1 ;CONVERT TO WORD INDEX
CMP BL,IOWSIZE ;IS THIS A LEGAL REQUEST?
JG IOWERR ;NO RETURN A WRITE ERROR
JMP WORD PTR CS:IOTAB[BX] ;YES, EXECUTE APPROPRIATE CODE
IOTAB DW IOWFLI ;FLUSH INPUT BUFFER
DW IOWFLO ;FLUSH OUTPUT BUFFER
DW IOWTIM ;SET IO TIMEOUT LIMIT
DW IOWSEO ;SET BYTEWISE MIDI OUTPUT/THRU FLAG
DW IOWCLO ;CLEAR POOR MANS OUTPUT/THRU MERGE FLAG
IOWSIZE EQU $-IOTAB
IOWFLI: ;FLUSH INPUT BUFFER
;PUSHF
CLI ;CLEAR INTERUPTS WHILE YOU
MOV AX,IFIRST[SI] ;MANIPULATE THE BUFFER POINTERS
MOV IAVAIL[SI],AX ;SET AVAIL EQUAL FIRST TO EMPTY
STI
;POPF ;ALL DONE AND CLEAR
MOV AX,0 ;EVERY TIME YOU FLUSH INPUT BUFFER,
MOV ERVAL[SI],AX ;YOU CAN CLEAR THE ERROR BITS
DEC AX ;AND RESET THE
MOV EROFF[SI],AX ;OFFSET TO ERROR TO AN ILEGAL VALUE
JMP IOWNEXT
IOWFLO: ;FLUSH OUTPUT BUFFER
;PUSHF
CLI ;CLEAR INTERUPTS WHILE YOU
MOV AX,OFIRST[SI] ;MANIPULATE THE BUFFER POINTERS
MOV OAVAIL[SI],AX ;SET AVAIL EQUAL FIRST TO EMPTY
STI
;POPF ;ALL DONE AND CLEAR
JMP IOWNEXT
IOWTIM: ;LOAD TIMEOUT LIMIT
MOV AX,ES:[DI] ;GET NEXT WORD FROM INPUT
ADD DI,2 ;SKIP WORD IN COMMAND STRING
SUB CX,2 ;COUNT THOSE BYTES OUT OF LOOP
JLE IOWERR ;IF THERE WEREN'T ENUF, SCREAM
MOV TIMEOUT[SI],AX ;STORE THEM IN STRUCTURE
JMP IOWNEXT
IOWSEO: ;SET BYTEWISE MIDI OUTPUT/THRU FLAG
OR STATUS[SI],OUTHRU ;SET THE OUT/THRU BIT IN STATUS
JMP IOWNEXT
IOWCLO: ;CLEAR POOR MANS MIDI OPUTPUT/THRU MERGE
AND STATUS[SI],NOT OUTHRU ;CLEAR THE BIT
JMP IOWNEXT
IOWNEXT:
LOOP IOWRITE ;LOOP FOR ALL IO CONTROL WRITE BYTES
MOV AX,STSDNE ;RETURN 0 FOR SUCCESS
JMP EXIT
IOWERR: ;I/O CONTROL WRITE ERROR RETURN
MOV AX,STSERR OR ERRBSL ;RETURN WRITE FAULT FOR THESE
JMP EXIT
SUBTTL RING BUFFER ROUTINES
PAGE
; LOCAL ROUTINES FOR MANAGING THE RING BUFFERS ON INPUT
; AND OUTPUT. THE FOLLOWING FOUR ROUTINES ARE ALL CALLED WITH THE
; SAME CONTEXT:
;
; DS:SI POINTS TO THE UNIT STRUCTURE FOR THIS UNIT
; AL IS THE CHARACTER TO BE PLACED IN OR REMOVED FROM A BUFFER
; AH IS THE RETURN STATUS FLAG: 0=SUCESS, -1=FAILURE
;
; ALL OTHER REGESTERS ARE PRESERVED.
;
PUT_OUT PROC NEAR ;PUTS AL INTO THE OUTPUT RING BUFFER
PUSH CX
PUSH DI
;PUSHF
CLI ;DISABLE INTERUPTS WHILE I HAVE OAVAIL
MOV CX,OAVAIL[SI] ;GET POINTER TO NEXT AVAILABLE BYTE IN
MOV DI,CX ;OUTPUT BUFFER.
INC CX ;INCRIMENT A COPY OF IT TO SEE IF THE
AND CX,BUFMSK ;BUFFER IS FULL.
CMP CX,OFIRST[SI] ;IS IT?
JE POERR ;YES, RETURN AN ERROR
ADD DI,OBUF[SI] ;NO, CALCULATE ACTUAL OFFSET OF CHAR
MOV [DI],AL ;AND STUFF THE CHARACTER INTO BUFFER
MOV OAVAIL[SI],CX ;UPDATE THE POINTER
MOV AH,0 ;INDICATE SUCCESS
JMP PORET ;AND RETURN
POERR:
MOV AH,-1 ;INDICATE FAILURE.
PORET:
STI
;POPF ;RE-ENABLE INTERUPTS
POP DI
POP CX
RET
PUT_OUT ENDP
GET_OUT PROC NEAR ;GETS THE NEXT CHARACTER FROM OUTPUT RING BUFFER
;SURE YOU DISABLE INTERUPTS FIRST.
PUSH CX
PUSH DI
;PUSHF ;JUST IN CASE, DISABLE INTERUPTS
CLI ;WHILE IN THIS ROUTINE.
MOV DI,OFIRST[SI] ;GET POINTER TO FIRST CHARACTER TO OUTPUT
CMP DI,OAVAIL[SI] ;IS THE BUFFER EMPTY?
JNE NGOERR ;NO.
MOV AH,-1 ;YES, INDICATE FAILURE
JMP GORET ;AND RETURN
NGOERR:
MOV CX,DI ;SAVE A COPY OF THE POINTER
ADD DI,OBUF[SI] ;CALCULATE ACTUAL ADDRESS
MOV AL,[DI] ;GET THE CHAR INTO AL
MOV AH,0 ;INDICATE SUCCESS.
INC CX ;INCRIMENT THE OFFSET
AND CX,BUFMSK ;MODULO 128
MOV OFIRST[SI],CX ;STORE BACK IN UNIT TABLE.
GORET:
;POPF
STI
POP DI
POP CX
RET
GET_OUT ENDP
PUT_IN PROC NEAR ;PUT THE CHAR FROM AL INTO INPUT RING BUFFER
PUSH CX
PUSH DI
;PUSHF ;DISABLE INTS WHILE IN THIS ROUTINE
CLI
MOV DI,IAVAIL[SI] ;GET POINTER TO NEXT AVAILABLE SLOT IN BUFFER
MOV CX,DI ;SAVE A COPY OF IT,
INC CX ;AND INCRIMENT THAT COPY (MODULO
AND CX,BUFMSK ;128) TO SEE IF THE BUFFER IS FULL.
CMP CX,IFIRST[SI] ;WELL, IS IT?
JNE NPIERR ;NO, THERE`S ROOM.
MOV AH,-1 ;YES, INDICATE FAILURE
JMP PIRET ;AND RETURN
NPIERR:
ADD DI,IBUF[SI] ;CALCULATE ACTUAL ADDRES,
MOV [DI],AL ;STORE THE CHARACTER THERE
MOV IAVAIL[SI],CX ;UPDATE THE POINTER.
MOV AH,0 ;AND INDICATE SUCCESS.
PIRET:
;POPF
STI
POP DI
POP CX
RET
PUT_IN ENDP
GET_IN PROC NEAR ;GETS ONE CARACTER FROM INPUT RING BUFFER INTO AL
PUSH CX
PUSH DI
;PUSHF
CLI ;DISABLE INTERUPTS WHILE I LOOK AT IFIRST.
MOV DI,IFIRST[SI] ;GET POINTER TO FIRST CHAR TO READ
CMP DI,IAVAIL[SI] ;IS THE BUFFER EMPTY?
JE GIERR ;THEN YOU CAN`T VERY WELL SQUEEZE WATER OUT OF IT
MOV CX,DI ;MAKE A COPY OF POINTER,
ADD DI,IBUF[SI] ;CALCULATE ACTUAL ADDRESS OF CHAR
MOV AL,[DI] ;GET THE CHAR INTO AL
MOV AH,0 ;INDICATE SUCCESS
INC CX ;INCRIMENT THAT COPY OF YOUR POINTER,
AND CX,BUFMSK ;MODULO THE BUFFER SIZE,
MOV IFIRST[SI],CX ;SO YOU CAN UPDATE THE POINTER.
JMP GIRET
GIERR:
MOV AH,-1 ;RETURN FAILURE INDICATOR
GIRET:
STI
;POPF ;RE-ENABLE INTERUPTS BEFORE YOU RETURN
POP DI
POP CX
RET
GET_IN ENDP
SUBTTL INTERUPT SERVICE ROUTINES
PAGE
;
; THESE ROUTINES ARE ONLY CALLED WHEN AN INTERUPT IS GENERATED
; BY THE UART.
; THESE INTERUPT ROUTINES ARE ENVOKED WHENEVER A CHAR ARRIVES IN THE
; UART, THE UART FINISHES SENDING A CHARACTER OUT, AN ERROR OCCURS
; WHILE READING A CHARACTER INTO THE UART.
MIDI_INT:
PUSH AX ;SAVE AX FOR THE USER,
MOV AL,020H ;OUTPUT A 20H TO THE UNDOCUMENTED INTERUPT
OUT 020H,AL ;CONTROL CHIP.
STI ;AND LET THE PROCESSOR INTERUPT ME.
PUSH BX ;SAVE THE REST OF THE REGESTERS.
PUSH CX
PUSH DX
PUSH SI
PUSH DI
PUSH DS ;SAVE THE DATA SEGMENT
PUSH CS ;SO YOU CAN LOAD CS
POP DS ;INTO DS AND FIND YOUR OWN STRUCTURES.
notify I
;
; THE FOLLOWING CODE FIGURES OUT WHICH (IF SEVERAL) DAISY-CHAINED
; DART CHIP FIRED THE INTERUPT, WHICH UART IN THE DART IT WAS,
; AND THE REASON FOR THE INTERUPT (RECEIVE, XMIT, ERROR).
;
MOV CX,MIDI_UNITS ;GET THE TOTAL NUMBER OF UARTS
MOV SI,OFFSET MIDI_TABA ;ADDRESS OF FIRST UARTS UNIT TABLE
INT_LUP: ;HEAD OF LOOP TO CHECK ALL UARTS
MOV DX,PORT[SI] ;GET THE PORT ADDRESS
ADD DX,MIDCON ;SLIDE UP TO COMMAND/STATUS REGESTER
MOV AL,0 ;MAKE SURE WE ARE TALKING TO REGESTER 0
OUT DX,AL
JMP SHORT $+2
IN AL,DX ;READ REGESTER 0 STATUS
TEST AL,INTPEND ;IS THIS DART CHIP THE ONE?
JNE THIS_DART ;YES, GO ON TO FIND THE PORT
ADD SI,UNIT_SIZE*2 ;NO, SKIP TWO UARTS TO NEXT WHOLE DART
SUB CX,2
JG INT_LUP ;IF THERE ARE ANY LEFT, KEEP TRYING
JMP INT_EXIT ;IF NOT, YOU'RE IN TROUBLE, BUT I IGNORE
THIS_DART:
OR DX,MIDB ;SLIDE UP TO UART B ON THIS DART
MOV AL,2 ;SET UP TO READ REGISTER 2
OUT DX,AL
JMP SHORT $+2
IN AL,DX ;READ IN VECTOR VALUE
TEST AL,VECB ;WAS THIS INTERUPT CAUSED BY UART B?
JNE A_INT ;NO, IT MUST BE UART A
DEC CX ;YES, MAKE SURE THERE IS A UART B STRUCTURE
JCXZ INT_EXIT ;IGNORE INTERUPTS FROM UNUSED UARTS
ADD SI,UNIT_SIZE ;GET UART B'S UNIT STRUCTURE
JMP INT_REASON ;WE'RE READY TO GO FIND OUT WHY
A_INT:
AND DX,MIDA ;CONVERT PORT NUMBER BACK TO UART A
INT_REASON:
AND AX,VECMASK ;WHACK OUT ONLY THE BITS THAT MATTER
MOV BX,AX ;PUT IT INTO AN INDEX REGESTER
JMP INT_FTAB[BX] ;JUMP TO THE APPROPRIATE ROUTINE
;
INT_FTAB DW INT_TXMIT ;TRANSMITTER HOLDING REGESTER EMPTY
DW INT_ERROR ;MODEM LINE INTERUPTS NOT IMPLIMENTED
DW INT_RECEIVE ;RECEIVER BUFFER HAS A CHARACTER
DW INT_ERROR ;RECEIVER FRAMING, OVER-RUN ERROR
INT_ERROR: ;ENTRYPOINT FOR SETTING BREAKPOINTS
notify E
NOP ;FOR ILLEGAL VECTOR VALUES
INT_EXIT:
MOV DX,PORT[SI] ;GET THE PORT NUMBER AGAIN
ADD DX,MIDCON ;CHANGE TO CONTROL PORT
AND DX,MIDA ;MAKE SURE IT'S CHANNEL A OF DART
MOV AL,ZREINT ;SEND THE SIMULATED Z80 RETURN
OUT DX,AL ;FROM INTERUPT COMMAND TO DART
;INTERUPT STUF WAS HERE
POP DS ;RECOVER ALL THE REGESTERS
POP DI
POP SI
POP DX
POP CX
POP BX
POP AX
IRET
;
; THE FOLLOWING INTERUPT SERVICE ROUTINES ALL HAVE THE
; SAME CONTEXT:
; -CS AND DS POINT TO THE DRIVER SEGMENT.
; -DS:[SI] POINTS TO THE UNIT STRUCTURE FOR THE ASYNC LINE THAT
; FIRED THE INTERUPT.
; -DX POINTS TO THE CONTROL REGESTER OF THE DART
; -AX BX CX AND DI ARE AVAILABLE FOR SCRATCH. ALL OTHERS
; MUST BE LEFT ALONE OR SAVED AND RECOVERED.
; TO EXIT FROM AN INTERUPT SERVICE ROUTINE, THESE SERVERS MUST
; JUMP TO INT_EXIT.
;
SUBTTL RECEIVER INTERUPT SERVICE ROUTINE
PAGE
INT_RECEIVE: ;THE PORT HAS RECEIVED A NEW CHARACTER, I MUST COPY IT
;INTO THE INPUT TYPEAHEAD BUFFER.
notify R
SUB DX,MIDCON ;POINT AT THE DATA REGESTER
IN AL,DX ;GET THE CHARACTER
CALL PUT_IN ;PUT THE CHARACTER IN THE RING BUFFER
CMP AH,0 ;WAS THERE ROOM?
JE REC_OK ;NO, SET FLAGS BEFORE RETURNING
notify O
OR ERVAL[SI],ERIBFO ;NO, SET THE OVERFLOW BIT
CMP EROFF[SI],-1 ;IF YOU HAVN'T INDICATED ANY ERRORS YET
JNE REC_OK
PUSH AX
MOV AX,IAVAIL[SI] ;THEN INDICATE THE POSITION THAT THE
MOV EROFF[SI],AX ;ERROR HAPPENED
POP AX
REC_OK:
TEST STATUS[SI],OUTHRU ;POOR MANS MIDI MERGE TURNED ON?
JZ NEXT_REC ;NOPE, DON'T ECHI THIS CHAR
CALL PUT_OUT ;YUP, SEND THE CHARACTER TO OUTPUT
CALL START_OUTPUT ;AND START THE TRANSMIT IF NECC.
NEXT_REC: ;CHECK TO SEE IF THERE ARE MORE CHARS READY
ADD DX,MIDCON ;POINT BACK AT THE COMMAND REGESTER
IN AL,DX ;READ REGESTER 0
TEST AL,RXCHAR ;IS THERE ANOTHER CHARACTER READY?
JNZ INT_RECEIVE ;YES, GO READ IT IN ALSO
MOV AL,ZRXINT ;NO, ENABLE INTERUPTS ON NEXT CHAR
OUT DX,AL ;THAT DOES COME IN.
JMP INT_EXIT
;
;
SUBTTL SPECIAL RECEIVE CONDITION INTERUPT
PAGE
;
; PARITY, FRAMING, OR OVERRUN ERROR INTERUPT
INT_RXSTAT:
notify S
MOV AL,1 ;REQUEST READ REGESTER 1
OUT DX,AL
JMP SHORT $+2
IN AL,DX ;READ IN THE VALUE
AND AX,070H ;CHOP OUT THE ERROR BITS
OR ERVAL[DI],AX ;ADD THOSE BITS TO THE ERROR WORD
CMP EROFF[SI],-1 ;CHECK TO SEE IF YOU'VE ALREADY
JNE RXSTAT_DONE ;TRAPPED ONE UNREPORTED ERROR
MOV AX,IAVAIL[SI] ;IF NOT, THEN POINT THE
MOV EROFF[SI],AX ;ERROR OFFSET AT NEXT AVAIL BYTE
RXSTAT_DONE:
MOV AL,ZERRES ;RESET THE ERROR BITS IN THE
OUT DX,AL ;UART.
JMP INT_EXIT
SUBTTL TRANSMITTER INTERUPT SERVICE ROUTINE
PAGE
; THE TRANSMITTER HOLDING REGESTER IS EMPTY, LOOK TO SEE IF
INT_TXMIT: ;THERE ARE MORE CHARS TO PRINT NOW.
notify T
AND STATUS[SI],NOT OUTINT ;CLEAR INTERUPT EXPECTED BIT.
CALL START_OUTPUT ;START THE NEXT CHARACTER
MOV AL,ZTXINT ;RESET THE UART TRANSMITTER
OUT DX,AL ;PENDING BIT
JMP INT_EXIT
;ROUTINE TO START THE NEXT CHARACTER PRINTING ON THE PORT, IF OUTPUT
;IS NOT BEING SUSPENDED FOR ONE REASON OR ANOTHER.
;THIS ROUTINE MAY BE CALLED FROM REQUEST ROUTINES, OR FROM INTERUPT
;SEVICE ROUTINES.
; THIS ROUTINE DESTROYS AX AND DX.
; SI MUST POINT AT THE UNIT STRUCTURE.
START_OUTPUT PROC NEAR
;PUSHF ;SAVE THE FLAGS SO I CAN
CLI ;DISABLE INTERUPTS
TEST STATUS[SI],OUTINT ;AM I IN HOLD OUTPUT MODE?
JNE DONT_START ;YES, DON'T SEND ANY MORE CHARS.
CALL GET_OUT ;CHECK TO SEE IF THERE IS A CHAR IN THE BUF
CMP AH,0 ;WELL, WAS THERE?
JNE DONT_START ;NO, BUFFER IS EMPTY
MOV DX,PORT[SI] ;YES, POINT DX AT THE TX OUT REGISTER
OUT DX,AL ;SEND HIM THE CHARACTER
OR STATUS[SI],OUTINT ;WARN EVERYBODY THAT I'M BUSY.
DONT_START:
;POPF
STI
RET
START_OUTPUT ENDP
;;
SUBTTL READ CLOCK CURRENT VALUE
PAGE
;
; THE GET_CLOCK ROUTINE RETURNS THE CURRENT CLOCK VALUE
; IN AX. REGESTER DX IS PRESERVED.
; This routine returns the "unsullied" clock value, so beware! The
; timer chip is a count DOWN timer, and you should calculate delta
; values accordingly. The C routines that read this clock invert
; the current value to make it look like an increasing time count,
; but down here in the driver I left the values raw.
;
GET_CLOCK PROC NEAR
PUSH DX
MOV DX,CLOCK_PORT ;GET PORT OF CLOCK CHIP
MOV AL,T2 OR LATCH ;TELL HIM YOU WANT TO LATCH TIMER 2
OUT DX,AL
JMP SHORT $+2
ADD DX,TIMER2 ;POINT DX AT TIMER2
IN AL,DX ;READ TIMER2 LSB
JMP SHORT $+2
MOV AH,AL
IN AL,DX ;READ TIMER2 MSB
XCHG AH,AL ;SWAP THE BYTES TO NORMAL
POP DX
RET
GET_CLOCK ENDP
;
;
; THE FOLLOWING LABEL DEFINES THE END OF THE DRIVER, SO I
; CAN TELL DOS HOW BIG I AM.
MIDI_END:
SUBTTL MIDI INITIALIZATION REQUEST ROUTINE
PAGE
;
; THE INITIALIZE DRIVER ROUTINES ARE STORED AFTER THE "END"
; OF THE DRIVER HERE SO THAT THIS CODE CAN BE THROWN AWAY AFTER
; THE DEVICE HAS BEEN INITIALIZED. THIS CODE IS ONLY CALLED TWICE:
; ONCE TO INITIALIZE EACH OF THE MIDI UNITS THAT THIS DRIVER
; CONTAINS. (APPARENTLY, MSDOS DOESN'T WRITE ANYTHING ON TOP OF
; THIS CODE UNTIL ALL UNITS ARE INITIALIZED.
; THE CONTEXT OF THE INITIALIZE CODE BELOW IS THE SAME AS
; ALL THE OTHER REQUEST ROUTINES EARLIER IN THE DRIVER.
;
INIT_TAB DB ZCHANR ;TABLE OF INITIALIZATION BYTES
DB 1,W1TXINT OR W1RX1ST OR W1STAT ;ENABLEL XMITR INTS
DB 3,W3RXEN OR W3RX8 ;ENABLE RECEIVING, 8 BITS/CHARACTER
DB 4,W4STOP1 OR W4X64 ;1 STOP BIT, X64 CLOCK MODE
DB 5,W5TXEN OR W5TX8 ;8 BITS/CHARACTER ON INPUT
DB ZRXINT ;ALLOW NEXT CHAR INPUT TO INTERUPT
INIT_SIZE EQU $-INIT_TAB ;COUNT THE BYTES FOR ME
;
; TABLE OF REGESTER OFFSETS AND VALUES FOR INITIALIZING THE CLOCK
CLK_TAB DB 0,T1 OR LMSB OR RATE ;TIMER ONE IS A RATE GENERATOR
DB TIMER1,0,0,8 ;THAT DIVIDES CLOCK BY 2048 (800H)
DB -TIMER1,T2 OR LMSB OR RATE ;TIMER TWO IS ALSO A RATE GENERATOR
DB TIMER2,0FFH,0,0FFH ;THAT DIVIDES BY THE LARGEST INT.
DB -TIMER2,T3 OR LMSB OR RATE ;TIMER 3 JUST COUNTS DOWN
DB TIMER3,0FFH,0,0FFH ;FROM LARGEST INTEGER (64K)
CLK_SIZE = $-CLK_TAB ;SIZE OF THIS TABLE
;
; INITIALIZE THE DRIVER AND DEVICE
;
MIDI_INIT:
MOV AX,OFFSET MIDI_END ;GET THE SIZE OF THE DRIVER
MOV ES:XFER[BX],AX ;SEND THAT BACK IN PACKET
MOV ES:XSEG[BX],CS ;SEND THE CODE SEGMENT ALSO.
;I HAVE SATISFIED ALL THE REQIREMENTS OF THE
;INIT FUNCTION TO RETURN IN THE I/O PACKET, SO
;I CAN DESTROY THE CONTENTS OF ES:BX AND USE
;THEM FOR OTHER THINGS.
MOV DX,PORT[SI] ;GET THE PORT ADDRESS OF THIS LINE
ADD DX,MIDCON ;SLIDE UP TO THE COMMAND REGESTER
; THE FOLLOWING CODE INITIALIZES THE Z8470 DART
; FOR INTERUPT DRIVEN MIDI OPERATION
MOV BX,OFFSET INIT_TAB ;GET ADDRESS OF TABLE
MOV CX,INIT_SIZE ;SIZE OF TABLE
INIT_PORT:
MOV AL,[BX] ;GET NEXT PORT CONTROL BYTE
OUT DX,AL ;SEND IT TO CONTROL REGESTER
JMP SHORT $+2
INC BX ;INCRIMENT TO NEXT BYTE
LOOP INIT_PORT ;LOOP UNTIL DONE
TEST DX,MIDB ;IF THIS IS UART B OF THE DART,
JNE DONE_INIT ;THEN YOU ARE DONE
;ONLY ONCE PER DART IS IT NESESSARY TO INITIALIZE
;THE VECTOR IN REGESTER 2
OR DX,MIDB ;SLIDE UP TO UART B
MOV AL,2 ;AND THEN INITIALIZE THE
OUT DX,AL ;WRITE REGESTER 2 WITH
JMP SHORT $+2
MOV AL,0 ;A ZERO VECTOR NUMBER
OUT DX,AL
;IT'S ONLY NECESSARY TO INITIALIZE THE INTERUPT
;VECTOR AND MASK ONCE, BUT I ALLOW THE FOLLOWING
;CODE TO EXECUTE ONCE FOR EVERY DART CHIP ON YOUR
;BOARD. IT CAN'T HURT.
MOV AX,0 ;POINT ES AT THE VECTOR SEGMENT
MOV ES,AX ;SO CAN INITIALIZE THE VECTORS
MOV AX,OFFSET MIDI_INT ;GET ADRS OF INTERUPT SERVICE ROUTINE
MOV DI,MIDI_VEC ;GET ADRS OF VECTOR
STOS WORD PTR [DI] ;STORE THE OFFSET THERE, THEN
MOV ES:[DI],CS ;THE SEGMENT IN THE FOLLOWING WORD.
MOV CX,DI ;CALCULATE THE VECTOR NUMBER:
SUB CL,022H ;SUBTRACT BIAS TO HARDWARE INTS,
SAR CL,1 ;DIVIDE BY 4 TO CONVERT TO
SAR CL,1 ;HARDWARE INTERUPT NUMBER.
MOV AH,1 ;SHIFT A MASK BY THAT MUCH TO
SAL AH,CL ;CREATE INTERUPT ENABLE MASK BIT,
NOT AH ;WHICH IS ACTIVE LOW...
IN AL,021H ;GET SYSTEM HARDWARE INTERUPT MASK
JMP SHORT $+2
AND AL,AH ;AND MY BIT OUT OF IT,
OUT 021H,AL ;WRITE IT BACK OUT AGAIN.
;
; THIS CODE INITIALIZES THE ON-BOARD CLOCK CHIP TO USEFUL
; MODES AND RATES FOR MIDI USE. TIMER1 IS USED TO DIVIDE THE CLOCK
; PULSE DOWN BY 2048 TO A 1024 TICKS PER SECOND (ABOUT 1 MILISECOND)
; RATE. THAT RATE FEEDS TIMER2 WHICH IS THE FIRST CLOCK VALUE READ.
; FOR LONG DELAY PERIODS, TIMER2 IS ASSUMED TO FEED TIMER3, SO IT
; WILL CONTAIN THE OVERFLOW, ALLOWING COUNTS UP TO 137 YEARS!
;
MOV DX,CLOCK_PORT ;GET CLOCK CONTROL REGESTER PORT
MOV BX,OFFSET CLK_TAB ;GET ADDRS OF TABLE OF COMMANDS
MOV CX,CLK_SIZE/2 ;SEND THIS MANY COMMANDS TO CLOCK
; SAR CX,1
CLK_LUP:
MOV AL,[BX] ;GET NEXT REGESTER OFFSET
CBW
ADD DX,AX ;AND OFFSET FROM IT
INC BX ;SLIDE UP TO NEXT VALUE
MOV AL,[BX] ;GET IT, AND
INC BX ;DON'T FORGET TO SKIP PAST
OUT DX,AL ;SEND VALUE TO CLOCK REGESTER
LOOP CLK_LUP
; THE FOLLOWING LOOP IS SUGESTED BY INTEL IN THE
; CLOCK CHIP SPEC.'S TO MAKE SURE THE CLOCK HAS
; SETTLED BEFORE YOU FIRST READ IT. I ONLY WATCH
; TIMER2 SETTLE, AND ASSUME THE REST ARE LONG GONE
MOV DX,CLOCK_PORT ;MAKE SURE YOU'RE LOOKING AT CONTROL
CLK_START:
MOV AL,T2 OR LATCH ;LATCH TIMER TWO
OUT DX,AL
JMP SHORT $+2
ADD DX,TIMER2 ;SLIDE UP TO IT'S DATA REGESTER
IN AL,DX ;READ AND
JMP SHORT $+2
MOV AH,AL ;SAVE LEASTMOST BYTE
IN AL,DX ;READ MOST SIGNIFICANT BYTE
SUB DX,TIMER2 ;SLIDE BACK TO CONTROL REGESTER
XCHG AL,AH ;MAKE INTO A BINARY NUMBER
NOT AX ;REVERSE INTO A UP-COUNTING NUMBER
OR AX,AX ;AND CHECK FOR ZERO
JNZ CLK_START ;WAIT FOR IT TO SETTLE DOWN
DONE_INIT:
MOV AX,STSDNE ;RETURN NO ERRORS.
JMP EXIT
DRIVER ENDS
END